# -*- coding : utf-8 -*-

import os
import wsgiref.handlers
import struct
import time
from StringIO import StringIO

from google.appengine.ext import webapp
from google.appengine.ext import db
from google.appengine.ext.webapp import template
from google.appengine.api import users
from google.appengine.api import images
from google.appengine.api import memcache

from functools import *


def getImageInfo(data): 
    data = str(data) 
    size = len(data) 
    height = -1 
    width = -1 
    content_type = '' 
    # handle GIFs 
    if (size >= 10) and data[:6] in ('GIF87a', 'GIF89a'): 
        # Check to see if content_type is correct 
        content_type = 'image/gif' 
        w, h = struct.unpack("<HH", data[6:10]) 
        width = int(w) 
        height = int(h) 
    # See PNG 2. Edition spec (http://www.w3.org/TR/PNG/) 
    # Bytes 0-7 are below, 4-byte chunk length, then 'IHDR' 
    # and finally the 4-byte width, height 
    elif ((size >= 24) and data.startswith('\211PNG\r\n\032\n') 
          and (data[12:16] == 'IHDR')): 
        content_type = 'image/png' 
        w, h = struct.unpack(">LL", data[16:24]) 
        width = int(w) 
        height = int(h) 
    # Maybe this is for an older PNG version. 
    elif (size >= 16) and data.startswith('\211PNG\r\n\032\n'): 
        # Check to see if we have the right content type 
        content_type = 'image/png' 
        w, h = struct.unpack(">LL", data[8:16]) 
        width = int(w) 
        height = int(h) 
    # handle JPEGs 
    elif (size >= 2) and data.startswith('\377\330'): 
        content_type = 'image/jpeg' 
        jpeg = StringIO(data) 
        jpeg.read(2) 
        b = jpeg.read(1) 
        try: 
            while (b and ord(b) != 0xDA): 
                while (ord(b) != 0xFF): b = jpeg.read 
                while (ord(b) == 0xFF): b = jpeg.read(1) 
                if (ord(b) >= 0xC0 and ord(b) <= 0xC3): 
                    jpeg.read(3) 
                    h, w = struct.unpack(">HH", jpeg.read(4)) 
                    break 
                else: 
                    jpeg.read(int(struct.unpack(">H", jpeg.read(2))[0])-2) 
                b = jpeg.read(1) 
            width = int(w) 
            height = int(h) 
        except struct.error: 
            pass 
        except ValueError: 
            pass 
    return content_type, width, height


class Image(db.Model):
    mime = db.StringProperty(required=True)
    data = db.BlobProperty(required=True)
    width = db.IntegerProperty(required=True)
    height = db.IntegerProperty(required=True)

class Entry(db.Model):
    comment = db.TextProperty(required=True)    
    date = db.DateTimeProperty(auto_now_add=True)
    
    image = db.ReferenceProperty(Image)
    image_size = db.IntegerProperty()
    image_width = db.IntegerProperty()
    image_height = db.IntegerProperty()
    
    parent_ref = db.SelfReferenceProperty()
    

    @property
    def replies(self, limit=10):
        replies = self.entry_set.order('date').fetch(limit=limit)
        if not replies:
            return
        elif not isinstance(replies, list):
            return [replies]
        return replies
    
    def delete(self):
        self.image.delete()
        replies = self.replies
        if replies:
            for reply in self.replies:
                reply.delete()
        super(Entry, self).delete()
        
    
class MainPage(webapp.RequestHandler):
    def get(self):
        if users.is_current_user_admin():
            admin = True
        else:
            admin = False
        
        reply = memcache.get('/')
        if reply and not admin:
            response, headers = reply

            try:
                if self.request.headers['If-Modified-Since'] == \
                        headers['Last-Modified']:
                    return self.error(304) # Object not modified
            except KeyError:
                pass
        else:
            entries = Entry.all().filter('parent_ref =', None).order('-date').fetch(30)
            tmpl = os.path.join(os.path.dirname(__file__), 'main.html')
            headers = {'Content-Type': 'text/html',
                       'Last-Modified': time.strftime('%a, %d %b %Y %H:%M:%S GMT',
                                                      time.gmtime())}
            response = template.render(tmpl, {'entries': entries,
                                              'admin': admin})

            if not admin:
                memcache.add('/', (response, headers))

        for header, argument in headers.items():
            self.response.headers[header] = argument
        return self.response.out.write(response)

class GetHandler(webapp.RequestHandler):
    
    def get(self, key):
        try:
            if users.is_current_user_admin():
                admin = True
            else:
                admin = False
                    
            response = memcache.get(key + self.request.query_string)
            if response and not admin:
                data, headers = response

                try:
                    if self.request.headers['If-Modified-Since'] == \
                            headers['Last-Modified']:
                        return self.error(304) # Object not modified
                except KeyError:
                    pass
                    
            else:
                kind = db.Key(key).kind()
                if kind == 'Image':
                    image_entity = Image.get(key)
                    if not image_entity:
                        self.error(404)
                        return self.response.out.write('Image not found')                

                    image_data = image_entity.data

                    width = self.request.get('w')
                    height = self.request.get('h')
                    
                    if width and height:
                        width=int(width)
                        height=int(height)
                        if image_entity.mime == 'image/png':
                            image_type = images.PNG
                        elif image_entity.mime == 'image/jpeg':
                            image_type = images.JPEG

                        if image_entity.width > width or image_entity.height > height:
                            image = images.Image(image_entity.data)
                            image.resize(width, height)
                            image_data = image.execute_transforms(image_type)

                    headers = {'Content-Type': str(image_entity.mime),
                               'Cache-Control': 'max-age=2592000'}
                    data = image_data

                    memcache.add(key + self.request.query_string,
                                    (data, headers))
            
                elif kind == 'Entry':
                    entry = Entry.get(key)
                    if not entry:
                        self.error(404)
                        return self.response.out.write('Entry not found')

                
                    tmpl = os.path.join(os.path.dirname(__file__), 'main.html')
                    headers = {'Content-Type': 'text/html',
                               'Last-Modified': time.strftime('%a, %d %b %Y %H:%M:%S GMT',
                                                      time.gmtime())}
                    data = template.render(tmpl, {'entries': [entry],
                                                        'parent': key,
                                                      'admin': admin})

                    if not admin:
                        memcache.add(key, (data, headers))
                    

            for header, argument in headers.items():
                self.response.headers[header] = argument
            return self.response.out.write(data)

        except db.BadKeyError:
            return self.error(404)


class DeleteEntryHandler(webapp.RequestHandler):
    
    def get(self, key):
        if not users.is_current_user_admin():
            self.error(400)
            return self.response.out.write('Access denied')
        entry = Entry.get(key)
        if not entry:
            self.error(404)
            return self.response.out.write('Entry not found')

        entry.delete()

        memcache.delete('/')
        if entry.parent_ref:
            memcache.delete(str(entry.parent_ref.key()))
            return self.redirect('/' + str(entry.parent_ref.key()))
        
        self.redirect('/')

class NewEntryHandler(webapp.RequestHandler):
            
    def post(self, reference):

        entry = Entry(comment=self.request.get("comment"))

        parent = self.request.get('parent')
        if parent:
            parent = Entry.get(parent)
            if parent:
                if parent.parent_ref:
                    #not a root
                    return self.error(500)
            entry.parent_ref = parent

        image_data = self.request.get("file")
        if image_data:
            if len(image_data) > 1000*1024:
                self.error(400)
                return self.response.out.write('Uploaded image too big')
        
            content_type, width, height = getImageInfo(image_data)
        
            if not content_type == 'image/png' and not content_type == 'image/jpeg':
                self.error(400)
                return self.response.out.write('Invalid image format')    


            image = images.Image(image_data)
            image_entity = Image(mime=content_type, data=db.Blob(image_data),
                                 width=image.width, height=image.height)
            entry.image_size = len(image_data)
            entry.image_width = width
            entry.image_height = height
            entry.image = image_entity.put()
        
        entry.put()

        memcache.delete('/')
        
        if parent:
            memcache.delete(str(parent.key()))
            return self.redirect('/' + str(parent.key()))
        return self.redirect('/')

class LoginHandler(webapp.RequestHandler):
    def get(self):
        self.redirect(users.create_login_url('/'))

class LogoutHandler(webapp.RequestHandler):
    def get(self):
        self.redirect(users.create_logout_url('/'))    

class MemcacheHandler(webapp.RequestHandler):
    def get(self):
        memcache.flush_all()
        self.redirect(users.create_logout_url('/'))  

def main():
    application = webapp.WSGIApplication([('/', MainPage),
                                          ('/([^/]+)', GetHandler),
                                          ('/new/([^/]*)', NewEntryHandler),
                                          ('/del/([^/]+)', DeleteEntryHandler),
                                          ('/login/', LoginHandler),
                                          ('/logout/', LogoutHandler),
                                          ('/flush/', MemcacheHandler)
                                         ], debug=True)
    wsgiref.handlers.CGIHandler().run(application)

if __name__ == "__main__":
    main()
